/*           Copyright(c) 2007-2008 Dave Sexton            *
 *                   XmlTreeView Control                   *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.XPath;
using System.Diagnostics;
using System.Threading;

namespace DaveSexton.Controls
{
	public enum NodePlacement
	{
		None,
		InsertBefore,
		InsertAfter,
		InsertChild
	}

	public partial class XmlTreeView : TreeView
	{
		#region Public Properties
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public virtual IXPathNavigable DataSource
		{
			get
			{
				return dataManager.DataSource;
			}
			set
			{
				dataManager.DataSource = value;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public virtual IXmlTreeViewDataManager DataManager
		{
			get
			{
				return dataManager;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				if (dataManager == value)
					return;
				else if (dataManager != null)
				{
					dataManager.DataSourceChanged -= dataManager_DataSourceChanged;
					dataManager.ElementChanged -= dataManager_ElementChanged;
				}

				dataManager = value;
				dataManager.DataSourceChanged += dataManager_DataSourceChanged;
				dataManager.ElementChanged += dataManager_ElementChanged;

				Bind();
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public virtual int NodePlacementMarkerOffsetLeft
		{
			get
			{
				if (CheckBoxes)
					return -22;
				else
					return -9;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public virtual int NodePlacementMarkerOffsetRight
		{
			get
			{
				return 5;
			}
		}

		[Category("Behavior"), DefaultValue(35),
		Description("The margin, in pixels, near the edge of the control where drag operations will automatically scroll hidden nodes into view near the mouse cursor.")]
		public virtual int AutoScrollDragMargin
		{
			get
			{
				return autoScrollDragMargin;
			}
			set
			{
				autoScrollDragMargin = value;
			}
		}

		[Browsable(false)]
		public bool CanDeleteSelectedNode
		{
			get
			{
				XmlTreeNode node = this.SelectedNode;

				return node != null && dataManager.CanDeleteElement(node, node.DataSource);
			}
		}

		[Browsable(false)]
		public bool IsCascadingChecks
		{
			get
			{
				return checkingRecursive;
			}
		}

		[Category("Behavior"), DefaultValue(true), Description("Specifies whether a message box will be shown to the user allowing confirmation before a node is deleted.")]
		public bool ConfirmNodeDeletion
		{
			get
			{
				return confirmDelete;
			}
			set
			{
				confirmDelete = value;
			}
		}

		[Browsable(false), DefaultValue(TreeViewDrawMode.OwnerDrawText),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new TreeViewDrawMode DrawMode
		{
			get
			{
				return base.DrawMode;
			}
			set
			{
				base.DrawMode = value;
			}
		}

		[DefaultValue(true)]
		public new bool AllowDrop
		{
			get
			{
				return base.AllowDrop;
			}
			set
			{
				base.AllowDrop = value;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new XmlTreeNode SelectedNode
		{
			get
			{
				return (XmlTreeNode) base.SelectedNode;
			}
			set
			{
				base.SelectedNode = value;
			}
		}
		#endregion

		#region Private / Protected
		internal readonly Dictionary<string, XmlTreeNode> nodeIndex = new Dictionary<string, XmlTreeNode>(256);

		private IXmlTreeViewDataManager dataManager;
		private XmlTreeNode currentDropNode, lastMouseOverDropNode, hoverDropNode, lastTargetDropNode, lastDragNode;
		private bool confirmDelete = true;
		private bool canInsertCurrentDropNodeAsSibling, canInsertCurrentDropNodeAsChild, dragging, checkingRecursive, boundToChildren, supressNodesChangedEvent, validDropTarget;
		private NodePlacement currentDropNodeMarker, lastDrogNodeMarker;
		private Point currentMousePosition;
		private Rectangle lastDrawnMarker;
		private int autoScrollDragMargin = 35, lastDrawnMarkerCount;
		private readonly System.Threading.Timer hoverTimer;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="XmlTreeView" /> class.
		/// </summary>
		public XmlTreeView()
		{
			InitializeComponent();

			base.DrawMode = TreeViewDrawMode.OwnerDrawText;
			base.AllowDrop = true;

			hoverTimer = new System.Threading.Timer(HoverTimerCallback, null, Timeout.Infinite, Timeout.Infinite);

			dataManager = new XmlTreeViewDataManager();
			dataManager.DataSourceChanged += dataManager_DataSourceChanged;
			dataManager.ElementChanged += dataManager_ElementChanged;
		}
		#endregion

		#region Methods
		#region Iteration and Navigation
		public XmlTreeNode GetNodeById(string id)
		{
			if (!string.IsNullOrEmpty("id") && nodeIndex.ContainsKey(id))
				return nodeIndex[id];
			else
				return null;
		}

		public TNode NextNode<TNode>(TNode from)
			where TNode : XmlTreeNode
		{
			if (from == null)
				throw new ArgumentNullException("from");

			if (from.Nodes.Count > 0)
				return (TNode) from.Nodes[0];
			else if (from.NextNode != null)
				return (TNode) from.NextNode;
			else if (from.Parent == null)
				// start at first sibling node (which may be "from" itself if it's the only node in the tree)
				return (TNode) this.Nodes[0];

			while (from.Parent != null && from.Parent.NextNode == null)
				from = (TNode) from.Parent;

			return (TNode) ((from.Parent == null) ? from : from.Parent.NextNode);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public TNode FindNode<TNode>(IXPathNavigable element)
				where TNode : XmlTreeNode
		{
			if (element == null)
				throw new ArgumentNullException("element");

			IXPathNavigable source = DataSource;

			if (source == null)
				throw new InvalidOperationException(Resources.Errors.NoDataSource);
			else
			{
				XPathNavigator sourceNavigator = source.CreateNavigator();
				XPathNavigator elementNavigator = element.CreateNavigator();

				if (sourceNavigator.IsSamePosition(elementNavigator))
				{
					if (boundToChildren)
						return null;
					else
						return (TNode) Nodes[0];
				}
				else
				{
					return FindNodeRecursive<TNode>(Nodes, delegate(TNode node)
						{
							return elementNavigator.IsSamePosition(node.DataSource.CreateNavigator());
						});
				}
			}
		}

		public TNode FindNode<TNode>(Predicate<TNode> predicate)
			where TNode : XmlTreeNode
		{
			if (predicate == null)
				throw new ArgumentNullException("predicate");

			return FindNodeRecursive(Nodes, predicate);
		}

		public TNode FindNode<TNode>(TNode startAfter, Predicate<TNode> predicate)
			where TNode : XmlTreeNode
		{
			if (startAfter == null)
				throw new ArgumentNullException("startAfter");

			if (predicate == null)
				throw new ArgumentNullException("predicate");

			TNode current = startAfter;

			while (true)
			{
				TNode found = FindNodeRecursive(current.Nodes, predicate);

				if (found != null)
					return found;

				TNode previous = current;
				current = (TNode) current.NextNode;

				if (current == null)
				// move to the next sibling of the current node's parent, or continue up until 
				// one is found or the root node is reached
				{
					while (previous.Parent != null &&
						(current = (TNode) previous.Parent.NextNode) == null)
					{
						previous = (TNode) previous.Parent;
					}
				}

				if (current == null)
				// previous is a root node; iterate all nodes until a match is found or the 
				// startAfter node is found
				{
					bool noMatches = false;

					found = FindNodeRecursive<TNode>(Nodes, delegate(TNode node)
					{
						if (predicate(node))
							return true;
						else if (node == startAfter)
						{
							noMatches = true;
							return true;
						}

						return false;
					});

					if (noMatches)
						return null;
					else if (found != null)
						return found;
				}
				else if (predicate(current))
					return current;
			}
		}

		public IEnumerable<TNode> FindNodes<TNode>(Predicate<TNode> predicate)
			where TNode : XmlTreeNode
		{
			if (predicate == null)
				throw new ArgumentNullException("predicate");

			return FindNodesRecursive(Nodes, predicate);
		}

		internal static TNode FindNodeRecursive<TNode>(System.Collections.IEnumerable nodes, Predicate<TNode> predicate)
			where TNode : XmlTreeNode
		{
			foreach (TNode node in nodes)
			{
				if (predicate(node))
					return node;

				TNode child = FindNodeRecursive(node.Nodes, predicate);

				if (child != null)
					return child;
			}

			return null;
		}

		internal static IEnumerable<TNode> FindNodesRecursive<TNode>(System.Collections.IEnumerable nodes, Predicate<TNode> predicate)
			where TNode : XmlTreeNode
		{
			foreach (TNode node in nodes)
			{
				if (predicate(node))
					yield return node;

				foreach (TNode child in FindNodesRecursive(node.Nodes, predicate))
					yield return child;
			}
		}

		public void ForEachNode<TNode>(Action<TNode> action)
			where TNode : XmlTreeNode
		{
			if (action == null)
				throw new ArgumentNullException("action");

			ForEachNodeRecursive(Nodes, action);
		}

		internal static void ForEachNodeRecursive<TNode>(System.Collections.IEnumerable nodes, Action<TNode> action)
			where TNode : XmlTreeNode
		{
			foreach (TNode node in nodes)
			{
				action(node);

				ForEachNodeRecursive(node.Nodes, action);
			}
		}
		#endregion

		public XmlTreeNode InsertNewNode(IXPathNavigable element)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			XmlTreeNode node = CreateNode(element, 0);
			
			if (node == null)
				throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
					Resources.Errors.NodeNotCreated, element.CreateNavigator().Name));

			Nodes.Insert(0, node);

			SelectedNode = node;

			if (dataManager.CanEditElement(node, node.DataSource))
				node.BeginEdit();

			OnNodesChanged(EventArgs.Empty);

			return node;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
		public XmlTreeNode InsertNewNode(XmlTreeNode parent, IXPathNavigable element)
		{
			if (parent == null)
				throw new ArgumentNullException("parent");

			if (element == null)
				throw new ArgumentNullException("element");

			XmlTreeNode node = CreateNode(element, 0);

			if (node == null)
				throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
					Resources.Errors.NodeNotCreated, element.CreateNavigator().Name));

			parent.Nodes.Insert(0, node);

			SelectedNode = node;

			if (dataManager.CanEditElement(node, node.DataSource))
				node.BeginEdit();

			OnNodesChanged(EventArgs.Empty);

			return node;
		}

		public bool DeleteNode(XmlTreeNode node, bool forceRecursive)
		{
			if (node == null)
				throw new ArgumentNullException("node");

			return DeleteNodeInternal(node, forceRecursive);
		}

		public bool DeleteSelectedNode(bool forceRecursive)
		{
			return DeleteNodeInternal(SelectedNode, forceRecursive);
		}

		private bool DeleteNodeInternal(XmlTreeNode node, bool forceRecursive)
		{
			if (node == null || !dataManager.CanDeleteElement(node, node.DataSource))
			{
				System.Media.SystemSounds.Beep.Play();
				return false;
			}

			TreeNodeCollection children = node.Nodes;

			bool deleteChildren = children.Count > 0 && (forceRecursive || dataManager.ShouldDeleteRecursive(node, node.DataSource));

			if (confirmDelete)
			{
				Control ctrl = this;

				while (ctrl.RightToLeft == RightToLeft.Inherit && ctrl.Parent != null)
					ctrl = ctrl.Parent;

				bool rtl = ctrl.RightToLeft == RightToLeft.Yes || (ctrl.RightToLeft == RightToLeft.Inherit && 
					System.Globalization.CultureInfo.CurrentCulture.TextInfo.IsRightToLeft);

				MessageBoxOptions options = (rtl)
					? MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading
					: (MessageBoxOptions) 0;

				if (MessageBox.Show((deleteChildren) ? Resources.Text.ConfirmDeleteRecursive : Resources.Text.ConfirmDelete, 
					Resources.Text.ConfirmDeleteCaption,
					MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1, options)
					== DialogResult.No)
					return false;
			}

			SuspendLayout();

			supressNodesChangedEvent = true;

			try
			{
				if (!deleteChildren)
				// insert the node's children into its parent, at the deleted node's index
				{
					XmlTreeNode parent = (XmlTreeNode) node.Parent;
					TreeNodeCollection parentNodes = (parent == null) ? this.Nodes : parent.Nodes;
					int index = node.Index;

					XmlTreeNode previous = (XmlTreeNode) node.PrevNode;

					IXPathNavigable anchorElement = (previous == null) ? null : previous.DataSource;

					while (children.Count > 0)
					{
						XmlTreeNode child = (XmlTreeNode) children[0];

						if (anchorElement == null)
						{
							if (parent == null)
								anchorElement = DataSource;
							else
								anchorElement = parent.DataSource;

							anchorElement = dataManager.InsertElementFirstChild(child.DataSource, anchorElement);
						}
						else
							anchorElement = dataManager.InsertElementAfter(child.DataSource, anchorElement);

						XmlTreeNode newNode = BindNode(child, anchorElement);

						children.Remove(newNode);
						parentNodes.Insert(index++, newNode);

						OnNodePlacementChanged(new XmlTreeNodePlacementEventArgs(child, newNode, NodePlacement.InsertChild, parent));
					}
				}

				if (dataManager.DeleteElement(node, node.DataSource))
				{
					node.Remove();

					OnNodesChanged(EventArgs.Empty);
				}
			}
			finally
			{
				supressNodesChangedEvent = false;
				ResumeLayout(true);
			}

			return true;
		}

		internal void NotifyNodeDataSourceChanged(XmlTreeNode node)
		{
			OnNodeDataSourceChanged(new XmlTreeNodeEventArgs(node));

			BindNode(node, node.DataSource);
		}

		public XmlTreeNode BindNode(IXPathNavigable originalElement, IXPathNavigable element)
		{
			if (originalElement == null)
				throw new ArgumentNullException("originalElement");

			if (element == null)
				throw new ArgumentNullException("element");

			return BindNode(FindNode<XmlTreeNode>(originalElement), element);
		}

		private XmlTreeNode BindNode(XmlTreeNode node, IXPathNavigable element)
		{
			XmlTreeNode newNode = null;
			TreeNode parent = node.Parent;

			if (parent == null)
				Bind();
			else
			{
				int index = node.Index;

				parent.Nodes.Remove(node);

				newNode = BindRecursive(element, parent.Nodes, index);

				if (!supressNodesChangedEvent)
					OnNodesChanged(EventArgs.Empty);
			}

			return newNode;
		}

		private void Bind()
		{
			IXPathNavigable dataSource = dataManager.DataSource;

			if (dataSource == null && Nodes.Count == 0)
				return;

			Nodes.Clear();

			if ((dataSource == null || Bind(dataSource)) && !supressNodesChangedEvent)
				OnNodesChanged(EventArgs.Empty);
		}

		private bool Bind(IXPathNavigable dataSource)
		{
			if (dataSource == null)
				return false;

			boundToChildren = dataManager.BindToDataSourceChildren;

			if (boundToChildren)
			{
				int i = 0;
				foreach (XPathNavigator child in dataSource.CreateNavigator().SelectChildren(XPathNodeType.Element))
					BindRecursive(child, this.Nodes, i++);

				if (i > 0)
					return true;
			}
			else
			{
				XmlTreeNode root = BindRecursive(dataSource, this.Nodes, this.Nodes.Count);

				if (root == null)
					throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
						Resources.Errors.NodeNotCreated, dataSource.CreateNavigator().Name));

				root.Expand();

				return true;
			}

			return false;
		}

		private XmlTreeNode BindRecursive(IXPathNavigable element, TreeNodeCollection nodes, int index)
		{
			XmlTreeNode newNode = CreateNode(element, index);

			if (newNode == null)
				return null;

			int childIndex = 0;
			foreach (IXPathNavigable child in element.CreateNavigator().SelectChildren(XPathNodeType.Element))
				BindRecursive(child, newNode.Nodes, childIndex++);

			nodes.Insert(index, newNode);

			return newNode;
		}

		protected virtual XmlTreeNode CreateNode(IXPathNavigable element, int index)
		{
			XPathNavigator navigator = element.CreateNavigator();

			Debug.Assert(DataSource.CreateNavigator().IsDescendant(navigator) || DataSource.CreateNavigator().IsSamePosition(navigator),
				"Element is not part of the data source.",
				"Element is not part of the data source: " + Environment.NewLine + Environment.NewLine +
				navigator.CreateNavigator().OuterXml.Substring(0, Math.Min(150, navigator.CreateNavigator().OuterXml.Length)));

			XmlTreeNodeCreatingEventArgs e = new XmlTreeNodeCreatingEventArgs(navigator);

			OnCreatingNode(e);

			if (e.Cancel)
				return null;

			XmlTreeNode node = e.NewNode ?? new XmlTreeNode(navigator);

			if (dataManager != null)
				dataManager.InitializeTreeNode(node, navigator);

			OnNodeCreated(new XmlTreeNodeCreatedEventArgs(node, index));

			Debug.Assert(DataSource.CreateNavigator().IsDescendant(node.DataSource.CreateNavigator()) || DataSource.CreateNavigator().IsSamePosition(node.DataSource.CreateNavigator()),
				"Node data source is not part of the tree's data source: " + node.Text,
				"Node data source is not part of the tree's data source: " + node.Text + Environment.NewLine + Environment.NewLine +
				node.DataSource.CreateNavigator().OuterXml.Substring(0, Math.Min(150, node.DataSource.CreateNavigator().OuterXml.Length)));

			return node;
		}

		private void EnsureCheckRecursive(XmlTreeNode node, bool check)
		{
			foreach (XmlTreeNode child in node.Nodes)
			{
				if (child.Checked != check)
					child.Checked = check;

				dataManager.CheckElement(child, child.DataSource, check);
				child.OnCheckedChanged(EventArgs.Empty);

				EnsureCheckRecursive(child, check);
			}
		}

		private static bool IsAncestor(XmlTreeNode ancestor, XmlTreeNode descendant)
		{
			XmlTreeNode parent = (XmlTreeNode) descendant.Parent;

			return parent != null && (ancestor == parent || IsAncestor(ancestor, parent));
		}

		/// <remarks>
		/// If the specified <paramref name="node"/> does not contain any visible children then
		/// the <paramref name="node"/> itself is returned.
		/// </remarks>
		private static XmlTreeNode GetLastVisibleChildRecursive(XmlTreeNode node)
		{
			if (node != null)
			{
				XmlTreeNode child = (XmlTreeNode) node.LastNode;

				while (child != null && !child.IsVisible)
					child = (XmlTreeNode) child.PrevNode;

				if (child != null)
					node = GetLastVisibleChildRecursive(child);
			}

			return node;
		}

		private bool DragItemStartInternal(XmlTreeNode dragNode)
		{
			return dataManager.CanMoveElement(dragNode, dragNode.DataSource);
		}

		private bool DragEnterItemInternal(XmlTreeNode dragNode, XmlTreeNode targetNode, Point mouse, out bool tryEnterAgain)
		{
			tryEnterAgain = false;

			XmlTreeNode targetParent = (XmlTreeNode) targetNode.Parent;

			IXPathNavigable targetElement = targetNode.DataSource;
			IXPathNavigable targetParentElement = (targetParent != null) ? targetParent.DataSource : null;
			IXPathNavigable dragElement = dragNode.DataSource;

			canInsertCurrentDropNodeAsSibling = dataManager.CanContainElement(dragNode, dragElement, targetParent, targetParentElement);
			canInsertCurrentDropNodeAsChild = dataManager.CanContainElement(dragNode, dragElement, targetNode, targetElement);

			if (!canInsertCurrentDropNodeAsSibling && !canInsertCurrentDropNodeAsChild)
				return false;

			Rectangle markerBounds = GetDropMarkerNearestPoint(targetNode, mouse, canInsertCurrentDropNodeAsSibling, canInsertCurrentDropNodeAsChild,
				out currentDropNodeMarker);

			if (canInsertCurrentDropNodeAsSibling)
			{
				if ((currentDropNodeMarker == NodePlacement.InsertBefore && targetNode.PrevNode != dragNode)
					|| (currentDropNodeMarker == NodePlacement.InsertAfter && targetNode.NextNode != dragNode))
				{
					Debug.WriteLine("Drag enter sibling, \"" + targetNode.Text + "\" at: " + currentDropNodeMarker.ToString());

					DrawReversibleDropMarker(targetNode, markerBounds);

					return true;
				}
				else
					// The target node is a valid drop target, just not for the current mouse position
					tryEnterAgain = true;
			}

			if (canInsertCurrentDropNodeAsChild)
			{
				if (currentDropNodeMarker == NodePlacement.InsertChild && targetNode.FirstNode != dragNode)
				{
					Debug.WriteLine("Drag enter parent, \"" + targetNode.Text + "\" at: " + currentDropNodeMarker.ToString());

					DrawReversibleDropMarker(targetNode, markerBounds);

					return true;
				}
				else
					// The target node is a valid drop target, just not for the current mouse position
					tryEnterAgain = true;
			}

			return false;
		}

		private bool DragOverItemInternal(XmlTreeNode dragNode, XmlTreeNode targetNode, Point mouse)
		{
			NodePlacement lastMarker = currentDropNodeMarker;

			GetDropMarkerNearestPoint(targetNode, mouse, canInsertCurrentDropNodeAsSibling, canInsertCurrentDropNodeAsChild,
				out currentDropNodeMarker);

			if ((currentDropNodeMarker == NodePlacement.InsertChild && targetNode.FirstNode != dragNode)
				|| (currentDropNodeMarker == NodePlacement.InsertBefore && targetNode.PrevNode != dragNode)
				|| (currentDropNodeMarker == NodePlacement.InsertAfter && targetNode.NextNode != dragNode))
			{
				if (currentDropNodeMarker != lastMarker)
				{
					Debug.WriteLine("Move \"" + targetNode.Text + "\" marker to: " + currentDropNodeMarker.ToString());

					if (lastMarker != NodePlacement.None)
						DragItemClearMarker(targetNode, GetDropMarkerBounds(targetNode, lastMarker));

					DrawReversibleDropMarker(targetNode, GetDropMarkerBounds(targetNode, currentDropNodeMarker));
				}

				return true;
			}

			return false;
		}

		private void DragLeaveItemInternal()
		{
			validDropTarget = false;

			// NOTE: This event is only raised when a drag operation ends on an invalid drop target or when the mouse leaves the TreeView control 
			// during a drag operation, but not when the mouse leaves each item.

			Debug.WriteLine("Drag left control bounds or dropped on invalid target");

			if (currentDropNode != null)
				DragLeaveItemInternal(currentDropNode);

			ResetDrag();
		}

		private void DragLeaveItemInternal(XmlTreeNode node)
		{
			Debug.WriteLine("Drag leave: \"" + node.Text + "\"; current marker: " + currentDropNodeMarker.ToString());

			DragItemClearMarker(node, currentDropNodeMarker);
		}

		private void DragLeaveItemInternal(XmlTreeNode node, NodePlacement marker)
		{
			Debug.WriteLine("Drag leave: \"" + node.Text + "\"; with marker: " + marker.ToString());

			DragItemClearMarker(node, marker);
		}

		private void DragLeaveItemInternal(XmlTreeNode node, Rectangle marker)
		{
			Debug.WriteLine("Drag leave: \"" + node.Text + "\"; with marker bounds: " + marker.ToString());

			DragItemClearMarker(node, marker);
		}

		private void DragItemClearMarker(XmlTreeNode node, NodePlacement marker)
		{
			Debug.WriteLine("\t\"" + node.Text + "\" clear marker: " + marker.ToString());

			currentDropNodeMarker = NodePlacement.None;

			DrawReversibleDropMarker(node, lastDrawnMarker);
		}

		private void DragItemClearMarker(XmlTreeNode node, Rectangle marker)
		{
			Debug.WriteLine("\t\"" + node.Text + "\" clear marker bounds: " + marker.ToString());

			currentDropNodeMarker = NodePlacement.None;

			DrawReversibleDropMarker(node, lastDrawnMarker);
		}

		private void ResetDrag()
		{
			dragging = false;
			currentMousePosition = Point.Empty;
			hoverDropNode = null;
			currentDropNode = null;
			currentDropNodeMarker = NodePlacement.None;
			lastMouseOverDropNode = null;
		}

		private bool CheckAutoScrollAtEdges(TreeNode node)
		{
			bool scrolled = false;

			int x = currentMousePosition.X;
			int y = currentMousePosition.Y;

			if (x < autoScrollDragMargin || x > Width - autoScrollDragMargin)
			{
				node.EnsureVisible();
				scrolled = true;
			}

			if (y < autoScrollDragMargin)
			{
				node = node.PrevVisibleNode;

				if (node != null)
				{
					node.EnsureVisible();
					scrolled = true;
				}
			}
			else if (y > Height - autoScrollDragMargin)
			{
				node = node.NextVisibleNode;

				if (node != null)
				{
					node.EnsureVisible();
					scrolled = true;
				}
			}

			return scrolled;
		}

		private void HoverTimerCallback(object state)
		{
			Invoke((MethodInvoker) delegate()
				{
					if (dragging)
					{
						TreeNode node = GetNodeAt(PointToClient(MousePosition));

						if (node == hoverDropNode)
							node.Expand();
					}
				});
		}

		private Rectangle GetDropMarkerNearestPoint(XmlTreeNode node, Point point, bool allowSibling, bool allowChild, out NodePlacement marker)
		{
			Rectangle bounds = node.Bounds;

			if (allowSibling && allowChild)
			{
				float thirdHeight = bounds.Height / 3;
				float firstThird = bounds.Top + thirdHeight;

				if (point.Y < firstThird)
				{
					marker = NodePlacement.InsertBefore;
					return GetDropMarkerBefore(node);
				}
				else if (point.Y >= firstThird && point.Y < bounds.Bottom - thirdHeight)
				{
					marker = NodePlacement.InsertChild;
					return GetDropMarkerChild(node);
				}
				else
				{
					marker = NodePlacement.InsertAfter;
					return GetDropMarkerAfter(node);
				}
			}
			else if (allowSibling)
			{
				if (point.Y < bounds.Top + bounds.Height / 2)
				{
					marker = NodePlacement.InsertBefore;
					return GetDropMarkerBefore(node);
				}
				else
				{
					marker = NodePlacement.InsertAfter;
					return GetDropMarkerAfter(node);
				}
			}
			// assume allowChild is true if allowSibling is false since this method will
			// not be invoked if both are false.
			else if (point.Y >= bounds.Top + bounds.Height / 3)
			{
				marker = NodePlacement.InsertChild;
				return GetDropMarkerChild(node);
			}
			else
				marker = NodePlacement.None;

			return Rectangle.Empty;
		}

		private Rectangle GetDropMarkerBounds(XmlTreeNode node, NodePlacement marker)
		{
			switch (marker)
			{
				case NodePlacement.InsertAfter:
					return GetDropMarkerAfter(node);
				case NodePlacement.InsertBefore:
					return GetDropMarkerBefore(node);
				case NodePlacement.InsertChild:
					return GetDropMarkerChild(node);
				case NodePlacement.None:
					return Rectangle.Empty;
				default:
					throw new InvalidEnumArgumentException("marker", (int) marker, typeof(NodePlacement));
			}
		}

		private Rectangle GetDropMarkerBefore(XmlTreeNode node)
		{
			Rectangle bounds = node.Bounds;

			bounds.Height = 1;

			int offsetLeft = NodePlacementMarkerOffsetLeft;

			if (node.ImageIndex > -1)
				offsetLeft -= ImageList.Images[node.ImageIndex].Width;
			else if (node.ImageKey != null)
				offsetLeft -= ImageList.Images[node.ImageKey].Width;

			// move to the left edge of the check box and adjust the top slightly
			bounds.Offset(offsetLeft, -1);

			// increase the length to the right edge of the tree
			bounds.Width = this.Width - NodePlacementMarkerOffsetRight - bounds.Left;

			return bounds;
		}

		private Rectangle GetDropMarkerAfter(XmlTreeNode node)
		{
			XmlTreeNode next = (XmlTreeNode) node.NextNode;

			while (next != null && !next.IsVisible)
				next = (XmlTreeNode) next.NextNode;

			if (next != null)
				return GetDropMarkerBefore(next);
			else
			{
				Rectangle bounds;

				if (node.GetNodeCount(false) == 0)
					bounds = node.Bounds;
				else
				{
					XmlTreeNode child = GetLastVisibleChildRecursive(node);

					if (child == null)
						bounds = node.Bounds;
					else
					{
						bounds = child.Bounds;
						bounds.Width += bounds.X - node.Bounds.X;
						bounds.X = node.Bounds.X;
					}
				}

				int offsetLeft = NodePlacementMarkerOffsetLeft;

				if (node.ImageIndex > -1)
					offsetLeft -= ImageList.Images[node.ImageIndex].Width;
				else if (node.ImageKey != null)
					offsetLeft -= ImageList.Images[node.ImageKey].Width;

				// move to the left edge of the check box and the bottom edge of the node
				bounds.Offset(offsetLeft, bounds.Height - 1);

				bounds.Height = 1;

				// increase the length to the right edge of the tree
				bounds.Width = this.Width - NodePlacementMarkerOffsetRight - bounds.Left;

				return bounds;
			}
		}

		private Rectangle GetDropMarkerChild(XmlTreeNode node)
		{
			if (node.IsExpanded)
			{
				XmlTreeNode child = (XmlTreeNode) node.FirstNode;

				if (child != null)
					return GetDropMarkerBefore(child);
			}

			Rectangle bounds = node.Bounds;

			// move to the right edge of its children's check box area and the bottom edge of the node
			bounds.Offset(-2, bounds.Height - 1);

			bounds.Height = 1;

			// increase the length to the right edge of the tree
			bounds.Width = this.Width - NodePlacementMarkerOffsetRight - bounds.Left;

			return bounds;
		}

		private void DrawNodeWithoutDropMarker(Graphics graphics, XmlTreeNode node, Rectangle bounds, Color color)
		{
			if (!node.IsEditing)
				TextRenderer.DrawText(graphics, node.Text, node.NodeFont ?? this.Font, bounds, color, TextFormatFlags.SingleLine);
		}

		private void DrawReversibleDropMarker(XmlTreeNode node, Rectangle marker)
		{
			if (marker != Rectangle.Empty)
			{
				if (lastDrawnMarker == marker)
					lastDrawnMarkerCount++;
				else
				{
					Debug.WriteLineIf(lastDrawnMarkerCount % 2 != 0, 
						"\t\"" + node.Text + "\" invalid marker draw count, " + lastDrawnMarkerCount + " for " + lastDrawnMarker.ToString() + 
						"; drawing marker: " + marker.ToString() + Environment.NewLine + Environment.StackTrace);

					lastDrawnMarker = marker;
					lastDrawnMarkerCount = 1;
				}

				Color backColor = (node.BackColor == Color.Empty) ? this.BackColor : node.BackColor;

				Point topLeft = PointToScreen(new Point(marker.Left, marker.Top));

				ControlPaint.DrawReversibleLine(topLeft, PointToScreen(new Point(marker.Right, marker.Top)), backColor);

				Rectangle spot = new Rectangle(topLeft.X - 5, topLeft.Y - 2, 5, 5);

				ControlPaint.FillReversibleRectangle(spot, backColor);

				Debug.WriteLine("\t\"" + node.Text + "\" draw reversible marker: " + marker.ToString() + "; last mouse: " + currentMousePosition.ToString());
			}
		}

		private void CompleteDropOperation(XmlTreeNode dragNode, NodePlacement marker, XmlTreeNode targetNode)
		{
			XmlTreeNodePlacementCancelEventArgs args = new XmlTreeNodePlacementCancelEventArgs(dragNode, marker, targetNode);

			OnNodePlacementChanging(args);

			if (args.Cancel)
				return;

			SuspendLayout();

			try
			{
				int targetNodeIndex;
				TreeNodeCollection targetNodeCollection;
				IXPathNavigable element;

				switch (marker)
				{
					case NodePlacement.InsertBefore:
						element = dataManager.InsertElementBefore(dragNode.DataSource, targetNode.DataSource);
						targetNodeIndex = targetNode.Index;
						targetNodeCollection = (targetNode.Parent == null) ? this.Nodes : targetNode.Parent.Nodes;
						break;
					case NodePlacement.InsertAfter:
						element = dataManager.InsertElementAfter(dragNode.DataSource, targetNode.DataSource);
						targetNodeIndex = targetNode.Index + 1;
						targetNodeCollection = (targetNode.Parent == null) ? this.Nodes : targetNode.Parent.Nodes;
						break;
					case NodePlacement.InsertChild:
						element = dataManager.InsertElementFirstChild(dragNode.DataSource, targetNode.DataSource);
						targetNodeIndex = 0;
						targetNodeCollection = targetNode.Nodes;
						break;
					default:
						return;
				}

				XmlTreeNode newNode = BindNode(dragNode, element);

				Debug.Assert(DataSource.CreateNavigator().IsDescendant(element.CreateNavigator()),
					"The element is not part of the tree's data source: " + dragNode.Text,
					"The element is not part of the tree's data source: " + dragNode.Text + Environment.NewLine + Environment.NewLine +
					element.CreateNavigator().OuterXml.Substring(0, Math.Min(150, element.CreateNavigator().OuterXml.Length)));

				if (newNode != null)
				{
					Debug.Assert(newNode.DataSource.CreateNavigator().IsSamePosition(element.CreateNavigator()),
						"Node data source did not remain the same while binding after a drag operation: " + newNode.Text,
						"Node data source did not remain the same while binding after a drag operation: " + newNode.Text + Environment.NewLine + Environment.NewLine +
						element.CreateNavigator().OuterXml.Substring(0, Math.Min(150, element.CreateNavigator().OuterXml.Length)));

					newNode.Remove();
					targetNodeCollection.Insert(targetNodeIndex, newNode);

					OnNodePlacementChanged(new XmlTreeNodePlacementEventArgs(dragNode, newNode, marker, targetNode));

					SelectedNode = newNode;

					newNode.EnsureVisible();
				}
			}
			finally
			{
				ResumeLayout(true);
			}
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (components != null)
					components.Dispose();

				if (hoverTimer != null)
					hoverTimer.Dispose();

				dataManager.DataSourceChanged -= dataManager_DataSourceChanged;
				dataManager.ElementChanged -= dataManager_ElementChanged;
			}

			base.Dispose(disposing);
		}
		#endregion

		#region Events
		private readonly object NodeDataSourceChangedEvent = new object();
		private readonly object NodeCreatedEvent = new object();
		private readonly object NodesChangedEvent = new object();
		private readonly object NodePlacementChangedEvent = new object();
		private readonly object NodePlacementChangingEvent = new object();
		private readonly object CreatingNodeEvent = new object();
		private readonly object ItemDropEvent = new object();

		/// <summary>
		/// Event raised when a drag operation has finished.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when a drag operation has finished.")]
		public event ItemDragEventHandler ItemDrop
		{
			add
			{
				lock (ItemDropEvent)
				{
					Events.AddHandler(ItemDropEvent, value);
				}
			}
			remove
			{
				lock (ItemDropEvent)
				{
					Events.RemoveHandler(ItemDropEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ItemDrop" /> event.
		/// </summary>
		/// <param name="e"><see cref="ItemDragEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnItemDrop(ItemDragEventArgs e)
		{
			ItemDragEventHandler handler = null;

			lock (ItemDropEvent)
			{
				handler = (ItemDragEventHandler) Events[ItemDropEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="XmlTreeView"/> controls needs to create a new <see cref="XmlTreeNode"/>.
		/// </summary>
		[Category("Data")]
		[Description("Event raised when the control needs to create a new tree node.")]
		public event EventHandler<XmlTreeNodeCreatingEventArgs> CreatingNode
		{
			add
			{
				lock (CreatingNodeEvent)
				{
					Events.AddHandler(CreatingNodeEvent, value);
				}
			}
			remove
			{
				lock (CreatingNodeEvent)
				{
					Events.RemoveHandler(CreatingNodeEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="CreatingNode" /> event.
		/// </summary>
		/// <param name="e"><see cref="XmlTreeNodeCreatingEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnCreatingNode(XmlTreeNodeCreatingEventArgs e)
		{
			EventHandler<XmlTreeNodeCreatingEventArgs> handler = null;

			lock (CreatingNodeEvent)
			{
				handler = (EventHandler<XmlTreeNodeCreatingEventArgs>) Events[CreatingNodeEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when a node's placement is being changed by a drag/drop operation.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when a node's placement is being changed by a drag/drop operation.")]
		public event EventHandler<XmlTreeNodePlacementCancelEventArgs> NodePlacementChanging
		{
			add
			{
				lock (NodePlacementChangingEvent)
				{
					Events.AddHandler(NodePlacementChangingEvent, value);
				}
			}
			remove
			{
				lock (NodePlacementChangingEvent)
				{
					Events.RemoveHandler(NodePlacementChangingEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="NodePlacementChanging" /> event.
		/// </summary>
		/// <param name="e"><see cref="XmlTreeNodePlacementEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnNodePlacementChanging(XmlTreeNodePlacementCancelEventArgs e)
		{
			EventHandler<XmlTreeNodePlacementCancelEventArgs> handler = null;

			lock (NodePlacementChangingEvent)
			{
				handler = (EventHandler<XmlTreeNodePlacementCancelEventArgs>) Events[NodePlacementChangingEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when a node's placement is changed by a drag/drop operation.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when a node's placement is changed by a drag/drop operation.")]
		public event EventHandler<XmlTreeNodePlacementEventArgs> NodePlacementChanged
		{
			add
			{
				lock (NodePlacementChangedEvent)
				{
					Events.AddHandler(NodePlacementChangedEvent, value);
				}
			}
			remove
			{
				lock (NodePlacementChangedEvent)
				{
					Events.RemoveHandler(NodePlacementChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="NodePlacementChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="XmlTreeNodePlacementEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnNodePlacementChanged(XmlTreeNodePlacementEventArgs e)
		{
			EventHandler<XmlTreeNodePlacementEventArgs> handler = null;

			lock (NodePlacementChangedEvent)
			{
				handler = (EventHandler<XmlTreeNodePlacementEventArgs>) Events[NodePlacementChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the values of one or more nodes have changed.
		/// </summary>
		[Category("Behavior")]
		[Description("Event raised when the values of one or more nodes have changed.")]
		public event EventHandler NodesChanged
		{
			add
			{
				lock (NodesChangedEvent)
				{
					Events.AddHandler(NodesChangedEvent, value);
				}
			}
			remove
			{
				lock (NodesChangedEvent)
				{
					Events.RemoveHandler(NodesChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="NodesChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnNodesChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (NodesChangedEvent)
			{
				handler = (EventHandler) Events[NodesChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised after a new <see cref="XmlTreeNode"/> is created.
		/// </summary>
		[Category("Data")]
		[Description("Event raised when a new XmlTreeNode is created.")]
		public event EventHandler<XmlTreeNodeCreatedEventArgs> NodeCreated
		{
			add
			{
				lock (NodeCreatedEvent)
				{
					Events.AddHandler(NodeCreatedEvent, value);
				}
			}
			remove
			{
				lock (NodeCreatedEvent)
				{
					Events.RemoveHandler(NodeCreatedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="NodeCreated" /> event.
		/// </summary>
		/// <param name="e"><see cref="XmlTreeNodeEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnNodeCreated(XmlTreeNodeCreatedEventArgs e)
		{
			EventHandler<XmlTreeNodeCreatedEventArgs> handler = null;

			lock (NodeCreatedEvent)
			{
				handler = (EventHandler<XmlTreeNodeCreatedEventArgs>) Events[NodeCreatedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when a node's <see cref="XmlTreeNode.DataSource">DataSource</see> property value changes.
		/// </summary>
		[Category("Data")]
		[Description("Event raised when a node's DataSource property value changes.")]
		public event EventHandler<XmlTreeNodeEventArgs> NodeDataSourceChanged
		{
			add
			{
				lock (NodeDataSourceChangedEvent)
				{
					Events.AddHandler(NodeDataSourceChangedEvent, value);
				}
			}
			remove
			{
				lock (NodeDataSourceChangedEvent)
				{
					Events.RemoveHandler(NodeDataSourceChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="NodeDataSourceChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="XmlTreeNodeEventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnNodeDataSourceChanged(XmlTreeNodeEventArgs e)
		{
			EventHandler<XmlTreeNodeEventArgs> handler = null;

			lock (NodeDataSourceChangedEvent)
			{
				handler = (EventHandler<XmlTreeNodeEventArgs>) Events[NodeDataSourceChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region Event Handlers
		private void dataManager_DataSourceChanged(object sender, EventArgs e)
		{
			Bind();
		}

		private void dataManager_ElementChanged(object sender, ElementChangedEventArgs e)
		{
			// handle the change events that are not caused by the tree view (they may be invoked by data manager implementations)
			switch (e.Change)
			{
				case ElementChangeType.Updated:
				case ElementChangeType.Replaced:
					BindNode(e.OriginalElement, e.Element);
					break;
			}
		}

		protected override void OnKeyUp(KeyEventArgs e)
		{
			XmlTreeNode node = SelectedNode;

			if (node != null && !node.IsEditing && e.KeyCode == Keys.Delete)
			{
				DeleteSelectedNode((Control.ModifierKeys & Keys.Shift) != 0);

				e.Handled = true;
			}

			base.OnKeyUp(e);
		}

		protected override void OnBeforeCheck(TreeViewCancelEventArgs e)
		{
			base.OnBeforeCheck(e);

			if (checkingRecursive)
				return;

			XmlTreeNode node = (XmlTreeNode) e.Node;
			IXPathNavigable element = node.DataSource;
			bool check = !node.Checked;

			dataManager.CheckElement(node, element, check);
			node.OnCheckedChanged(EventArgs.Empty);

			SuspendLayout();

			checkingRecursive = true;

			try
			{
				if (dataManager.ShouldCheckRecursive(node, element))
					EnsureCheckRecursive(node, check);
			}
			finally
			{
				checkingRecursive = false;
				ResumeLayout(true);
			}

			OnNodesChanged(EventArgs.Empty);
		}

		protected override void OnBeforeLabelEdit(NodeLabelEditEventArgs e)
		{
			base.OnBeforeLabelEdit(e);

			if (!e.CancelEdit)
			{
				XmlTreeNode node = (XmlTreeNode) e.Node;

				if (!dataManager.CanEditElement(node, node.DataSource))
					e.CancelEdit = true;
			}
		}

		protected override void OnAfterLabelEdit(NodeLabelEditEventArgs e)
		{
			base.OnAfterLabelEdit(e);

			if (!e.CancelEdit && e.Label != null)
			// NOTE: A null label occurred during testing when the edit was committed without any text being entered.
			{
				XmlTreeNode node = (XmlTreeNode) e.Node;

				dataManager.SetElementLabel(node, node.DataSource, e.Label);

				OnNodesChanged(EventArgs.Empty);
			}
		}

		protected override void OnItemDrag(ItemDragEventArgs e)
		{
			base.OnItemDrag(e);

			XmlTreeNode node = (XmlTreeNode) e.Item;

			if (node == null || !DragItemStartInternal(node))
				return;

			dragging = true;

			try
			{
				// NOTE: Conrols may depend on the selected node not changing before the ItemDrag event is raised 
				// so do not assign it until base.OnItemDrag(e) has been called.  This is because a control may
				// want to reselect the originally selected node after the drag operation.  During the ItemDrag event
				// a handler can access the node being dragged through the ItemDragEventArgs.Item property, so the ability
				// exists to get both of the node references involved (i.e., originally selected and currently dragging).
				SelectedNode = node;

				if (DoDragDrop(node.Id, DragDropEffects.All) != DragDropEffects.None)
				{
					if (validDropTarget && lastDragNode != null && lastTargetDropNode != null)
						// internal drop
						CompleteDropOperation(lastDragNode, lastDrogNodeMarker, lastTargetDropNode);
				}
			}
			finally
			{
				dragging = false;
			}

			OnItemDrop(e);
		}

		protected override void OnDragEnter(DragEventArgs drgevent)
		{
			base.OnDragEnter(drgevent);

			// NOTE: This event is only raised when a drag operation begins or when the mouse enters the TreeView control during a drag operation, 
			// but not when each item is entered.  Leaving and reentering the TreeView control during the same drag operation will also raise the event.

			Debug.WriteLine("Drag started or entered control bounds");

			if (drgevent.Data.GetDataPresent("Text"))
			{
				XmlTreeNode dragNode = this.GetNodeById((string) drgevent.Data.GetData("Text"));

				dragging = dragNode != null && dragNode.TreeView == this;
			}
		}

		protected override void OnDragLeave(EventArgs e)
		{
			DragLeaveItemInternal();

			base.OnDragLeave(e);
		}

		protected override void OnDragOver(DragEventArgs drgevent)
		{
			base.OnDragOver(drgevent);

			drgevent.Effect = DragDropEffects.None;

			if (!drgevent.Data.GetDataPresent("Text"))
				return;

			XmlTreeNode dragNode = this.GetNodeById((string) drgevent.Data.GetData("Text"));

			if (dragNode == null || dragNode.TreeView != this)
				return;

			currentMousePosition = this.PointToClient(new Point(drgevent.X, drgevent.Y));

			XmlTreeNode targetNode = (XmlTreeNode) this.GetNodeAt(currentMousePosition);

			if (targetNode == null)
			{
				DragLeaveItemInternal();
				return;
			}

			bool scrollChanged = CheckAutoScrollAtEdges(targetNode);

			if (hoverDropNode != targetNode)
			{
				hoverDropNode = targetNode;

				if (!targetNode.IsExpanded)
					hoverTimer.Change(750, System.Threading.Timeout.Infinite);
			}

			bool tryEnterAgain = false;

			if (targetNode == currentDropNode)
			{
				// currentDropNodeMarker is modified by the DragOverItemInternal method, but its original value
				// is required if the method returns false
				NodePlacement marker = currentDropNodeMarker;

				if (DragOverItemInternal(dragNode, targetNode, currentMousePosition))
					drgevent.Effect = DragDropEffects.Move;
				else
				{
					DragLeaveItemInternal(currentDropNode, marker);

					// The mouse is over a node that is a valid drop target, just not at the current mouse position.
					// (i.e., InsertBefore, InsertAfter or InsertChild may be supported at a different location on this node)
					tryEnterAgain = true;
				}
			}
			else
			{
				if (currentDropNode != null)
				{
					if (scrollChanged)
						DragLeaveItemInternal(currentDropNode, lastDrawnMarker);
					else
						DragLeaveItemInternal(currentDropNode);
				}

				if (targetNode != lastMouseOverDropNode && targetNode != dragNode && !IsAncestor(dragNode, targetNode))
				{
					// NOTE: tree_DrawNode requires currentDropNode to be assigned before it's invoked so this assignment is 
					// required before calling DragEnterItemInternal, which may repaint the node.
					currentDropNode = targetNode;

					if (DragEnterItemInternal(dragNode, targetNode, currentMousePosition, out tryEnterAgain))
						drgevent.Effect = DragDropEffects.Move;
					else
					{
						currentDropNode = null;
						currentDropNodeMarker = NodePlacement.None;
					}
				}
			}

			// The lastMouseOverDropNode variable is used to determine whether DragEnterItemInternal can be called again for the same item.
			// This is useful, for example, if an item is not a valid drop target while the mouse pointer is near the "before" marker, but
			// will be a valid drop target if the mouse moves near the "after" marker.  Setting the variable to null will cause the method 
			// to be invoked for the node until either the mouse leaves the node or it becomes a valid drop target.
			lastMouseOverDropNode = (tryEnterAgain) ? null : targetNode;
		}

		protected override void OnDragDrop(DragEventArgs drgevent)
		{
			base.OnDragDrop(drgevent);

			lastDragNode = this.GetNodeById((string) drgevent.Data.GetData("Text"));

			Point point = this.PointToClient(new Point(drgevent.X, drgevent.Y));

			lastTargetDropNode = (XmlTreeNode) this.GetNodeAt(point);
			lastDrogNodeMarker = currentDropNodeMarker;

			validDropTarget = lastTargetDropNode != null;

			Debug.WriteLine("\"" + lastDragNode.Text + "\" dropped; " + lastDrogNodeMarker.ToString() + 
				" target: \"" + ((lastTargetDropNode != null) ? lastTargetDropNode.Text : "") + "\"");

			if (validDropTarget)
				DragItemClearMarker(lastTargetDropNode, lastDrogNodeMarker);

			ResetDrag();
		}

		protected override void OnDrawNode(DrawTreeNodeEventArgs e)
		{
			base.OnDrawNode(e);

			XmlTreeNode node = (XmlTreeNode) e.Node;
			Rectangle labelBounds = e.Bounds;

			if (labelBounds.X < 0 && labelBounds.Y == 0 && node != GetNodeAt(labelBounds.Location))
			// this occurred in testing when a node is expanded
			{
				e.DrawDefault = true;
				return;
			}

			using (Brush brush = new SolidBrush((node.BackColor == Color.Empty) ? this.BackColor : node.BackColor))
			{
				Rectangle background = labelBounds;
				background.Offset(1, 0);

				e.Graphics.FillRectangle(brush, background);
			}

			Color color = (node.ForeColor == Color.Empty) ? this.ForeColor : node.ForeColor;

			if ((e.State & TreeNodeStates.Selected) != 0)
			{
				e.Graphics.FillRectangle(SystemBrushes.Highlight, labelBounds);
				color = SystemColors.HighlightText;
			}

			DrawNodeWithoutDropMarker(e.Graphics, node, labelBounds, color);

			if ((e.State & TreeNodeStates.Focused) != 0)
				ControlPaint.DrawFocusRectangle(e.Graphics, labelBounds);
		}
		#endregion
	}
}
